import * as method from "@mac-xiang/method";
import * as crypto from "crypto";
import axios from "axios";
import { oa } from "../itf";
import { REQUEST, ORDER } from "../struct";
// import qs from "qs";
import * as itf from "./itf";

export class PAY {
  config: itf.Config;
  constructor (param: itf.Create) {
    this.config = Object.assign({
      version: "5.1.0",
      encoding: "utf-8",
      signMethod: "01",
      merId: param.appId
    }, param);
  }
  create(p: ORDER, debug?: any) { // 创建订单
    let time = +new Date;
    if (!p.frontUrl) p.frontUrl = this.config.frontUrl;
    if (!p.backUrl) p.backUrl = this.config.backUrl;
    if (p.channelType != "08") p.channelType = "07";
    if (!p.accessType) p.accessType = "0";
    if (!p.order) p.order = method.randStr("yl");
    if (!p.txnTime) p.txnTime = method.formatDate(time, "yyyyMMDDHHmmss");
    time += 300000;
    if (!p.payTimeout) p.payTimeout = method.formatDate(time, "yyyyMMDDHHmmss");
    if (!p.title) p.title = "支付定金";

    const d: itf.ORDER = {
      version: this.config.version,
      encoding: this.config.encoding,
      signMethod: this.config.signMethod, // 非对称签名： 01（表示采用RSA签名） HASH表示散列算法 11：支持散列方式验证SHA-256 12：支持散列方式验证SM3
      txnType: "01", // 交易类型
      txnSubType: "01", // 01：自助消费，通过地址的方式区分前台消费和后台消费（含无跳转支付） 03：分期付款
      bizType: "000201", // 产品类型
      frontUrl: p.frontUrl,
      backUrl: p.backUrl,
      channelType: p.channelType,
      accessType: p.accessType,
      currencyCode: "156",
      merId: this.config.merId,
      orderId: p.order,
      txnTime: p.txnTime,
      txnAmt: p.money.toString(),
      payTimeout: p.payTimeout,
      riskRateInfo: `{commodityName=${p.title}}`,
    };
    this.sign(d);
    p.order = d.orderId;
    p.channelType = d.channelType;
    p.txnTime = d.txnTime;
    p.respCode = "00";
    p.body = this.createForm(d, this.config.url.front, "post", debug);
    return new REQUEST(p);
  }
  private get txnTime() {
    return method.formatDate(new Date, "yyyyMMDDHHmmss");
  }
  private sign(p: itf.QueryPublic) {
    delete p.signature;
    let ret = "";
    switch (p.signMethod) {
      case "01": // 固定为01
        p.certId = this.config.private.id;
        ret = this.getSign(p, this.config.private.key);
        break;
      case "11": // 支持散列方式验证SHA-256 官网说明文档不够清晰.
        p.certId = this.config.private.id;
        ret = this.getSign(p, this.config.private.key);
        break;
      case "12": // 支持散列方式验证SM3 官网文档太过操蛋
        break;

      default:
        break;
    }
    return ret;
  }
  private getSign(p: { [prop: string]: any; }, privateKey: string) {
    const ps = this.signStr(p);
    const signStr = crypto.createHash('sha256').update(ps).digest("hex");
    switch (p.signMethod) {
      case "01":
        p.signature = crypto.createSign("sha256")
          .update(signStr, "utf8").sign(privateKey, "base64");
        break;
      case "11": // 没用
        console.log("散列算法");
        p.signature = crypto.createHash("sha256")
          .update(ps)
          .digest("hex");
        break;

      default:
        break;
    }
    // console.log(qs.stringify(p));
    // return qs.stringify(p);
    return ps + "&signature=" + encodeURIComponent(p.signature);
  }
  private createForm(p: { [prop: string]: any; }, url: string, formMethod: "post" | "get" = "post", debug?: any) {
    // const fid = method.randStr("");
    let r = "<html><head><meta http-equiv=\"Content-Type\" content=\"text/html; charset=utf-8\" /></head>";
    r += debug ? `<body>` : `<body onload="javascript:document.pay_form.submit();">`;
    r += `<form id="pay_form" name="pay_form" action="${url}" method="${formMethod}">\n`;
    Object.keys(p).forEach(k => {
      r += `<input type="hidden" name="${k}" id="${k}" value="${p[k]}" />\n`;
    });
    if (debug) r += `<button onclick="javascript:document.pay_form.submit();">提交</button>`;
    r += "</form></body></html>";
    return r;
  }
  private signStr(p: { [prop: string]: string; }) {
    return Object.keys(p).sort().map((k: string) => {
      return `${k}=${p[k]}`;
    }).join("&");
  }
  private qsparse(p: string) {
    let s = p.replace(/\r/g, ""), ret: oa = {};
    p.split("&").forEach(a => {
      let i = a.indexOf("=");
      ret[a.substr(0, i++)] = a.substr(i);
    });
    return ret;
  }
  private validate(param: string | oa): REQUEST {
    let ret: oa = { err: "数据错误", raw: param };
    const p: oa = typeof param == "string" ? this.qsparse(param) : Object.assign({}, param);
    if (p.signature) {
      const signature = p.signature;
      delete p.signature;

      const pk = "-----BEGIN CERTIFICATE-----\r\nMIIEYzCCA0ugAwIBAgIFEDkwhTQwDQYJKoZIhvcNAQEFBQAwWDELMAkGA1UEBhMC\r\nQ04xMDAuBgNVBAoTJ0NoaW5hIEZpbmFuY2lhbCBDZXJ0aWZpY2F0aW9uIEF1dGhv\r\ncml0eTEXMBUGA1UEAxMOQ0ZDQSBURVNUIE9DQTEwHhcNMjAwNzMxMDExOTE2WhcN\r\nMjUwNzMxMDExOTE2WjCBljELMAkGA1UEBhMCY24xEjAQBgNVBAoTCUNGQ0EgT0NB\r\nMTEWMBQGA1UECxMNTG9jYWwgUkEgT0NBMTEUMBIGA1UECxMLRW50ZXJwcmlzZXMx\r\nRTBDBgNVBAMMPDA0MUA4MzEwMDAwMDAwMDgzMDQwQOS4reWbvemTtuiBlOiCoeS7\r\nveaciemZkOWFrOWPuEAwMDAxNjQ5NTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCC\r\nAQoCggEBAMHNa81t44KBfUWUgZhb1YTx3nO9DeagzBO5ZEE9UZkdK5+2IpuYi48w\r\neYisCaLpLuhrwTced19w2UR5hVrc29aa2TxMvQH9s74bsAy7mqUJX+mPd6KThmCr\r\nt5LriSQ7rDlD0MALq3yimLvkEdwYJnvyzA6CpHntP728HIGTXZH6zOL0OAvTnP8u\r\nRCHZ8sXJPFUkZcbG3oVpdXQTJVlISZUUUhsfSsNdvRDrcKYY+bDWTMEcG8ZuMZzL\r\ng0N+/spSwB8eWz+4P87nGFVlBMviBmJJX8u05oOXPyIcZu+CWybFQVcS2sMWDVZy\r\nsPeT3tPuBDbFWmKQYuu+gT83PM3G6zMCAwEAAaOB9DCB8TAfBgNVHSMEGDAWgBTP\r\ncJ1h6518Lrj3ywJA9wmd/jN0gDBIBgNVHSAEQTA/MD0GCGCBHIbvKgEBMDEwLwYI\r\nKwYBBQUHAgEWI2h0dHA6Ly93d3cuY2ZjYS5jb20uY24vdXMvdXMtMTQuaHRtMDkG\r\nA1UdHwQyMDAwLqAsoCqGKGh0dHA6Ly91Y3JsLmNmY2EuY29tLmNuL1JTQS9jcmw3\r\nNTAwMy5jcmwwCwYDVR0PBAQDAgPoMB0GA1UdDgQWBBTmzk7XEM/J/sd+wPrMils3\r\n9rJ2/DAdBgNVHSUEFjAUBggrBgEFBQcDAgYIKwYBBQUHAwQwDQYJKoZIhvcNAQEF\r\nBQADggEBAJLbXxbJaFngROADdNmNUyVxPtbAvK32Ia0EjgDh/vjn1hpRNgvL4flH\r\nNsGNttCy8afLJcH8UnFJyGLas8v/P3UKXTJtgrOj1mtothv7CQa4LUYhzrVw3UhL\r\n4L1CTtmE6D1Kf3+c2Fj6TneK+MoK9AuckySjK5at6a2GQi18Y27gVF88Nk8bp1lJ\r\nvzOwPKd8R7iGFotuF4/8GGhBKR4k46EYnKCodyIhNpPdQfpaN5AKeS7xeLSbFvPJ\r\nHYrtBsI48jUK/WKtWBJWhFH+Gty+GWX0e5n2QHXHW6qH62M0lDo7OYeyBvG1mh9u\r\nQ0C300Eo+XOoO4M1WvsRBAF13g9RPSw=\r\n-----END CERTIFICATE-----";
      const publicKey = p.signPubKeyCert;
      // .replace(/ /g, "+")
      // .replace(/BEGIN\+/, "BEGIN ")
      // .replace(/END\+/, "END ");

      const params_sha256x16 = crypto.createHash("sha256")
        .update(this.signStr(p))
        .digest("hex");
      const verifier = crypto.createVerify('RSA-SHA256');
      verifier.update(Buffer.from(params_sha256x16, 'utf-8'));
      let result: any;
      try {
        result = verifier.verify(publicKey, signature, "base64");
      } catch (error) {
        console.log(error.stack);
      }
      if (result) {
        // console.log("验签通过", publicKey == pk);
        delete p.signPubKeyCert;
        ret = p;
      } else {
        ret = { err: "验签失败", raw: param };
      }
      // console.log(signature);
    }
    return new REQUEST(ret);
  }
  query(param: ORDER | string): Promise<REQUEST> {
    return new Promise((resolve) => {
      const p = new ORDER(typeof param == "string" ? { order: param } : param);
      let r = new REQUEST({ err: "参数不正确", raw: p });
      if (!p.order) return r;
      if (!p.txnTime) p.txnTime = this.txnTime;

      if (p.channelType !== "07") p.channelType = "08";
      const d: itf.Query = {
        version: this.config.version, // 版本号
        encoding: this.config.encoding,  // 编码方式
        signMethod: this.config.signMethod, // 签名方法
        txnType: "00", // 交易类型
        txnSubType: "00", // 交易子类
        bizType: "000000", // 业务类型
        accessType: "0", // 接入类型
        channelType: p.channelType,
        orderId: p.order, // 请修改被查询的交易的订单号，8-32位数字字母，不能含“-”或“_”
        merId: this.config.merId, // 商户id
        txnTime: p.txnTime ? p.txnTime : this.txnTime, // 被查询的交易的订单发送时间，格式为YYYYMMDDhhmmss
      };
      // console.log(this.config.url.singleQuery);
      axios({
        method: "post",
        url: this.config.url.singleQuery,
        data: this.sign(d),
      }).then(r => {
        resolve(this.validate(r.data));
      }).catch(e => {
        resolve(new REQUEST({ err: "网络错误", raw: e }));
      });
    });

  }
  private revoke(p: ORDER): Promise<REQUEST> {
    return new Promise(async resolve => {
      if (!p.frontUrl) p.frontUrl = this.config.frontUrl;
      if (!p.backUrl) p.backUrl = this.config.backUrl;
      if (p.channelType != "08") p.channelType = "07";
      if (!p.accessType) p.accessType = "0";
      if (!p.txnTime) p.txnTime = this.txnTime;
      if (!p.title) p.title = "支付定金";

      const q: itf.Back = {
        version: this.config.version,
        encoding: this.config.encoding,
        signMethod: this.config.signMethod,
        txnType: "31",
        txnSubType: "00",
        bizType: "000201",
        accessType: "0",
        channelType: p.channelType,
        backUrl: p.backUrl,
        orderId: p.orderT as string, // 请修改被查询的交易的订单号，8-32位数字字母，不能含“-”或“_”
        merId: this.config.merId, // 商户id
        origQryId: p.queryId,
        txnTime: p.txnTime, // 被查询的交易的订单发送时间，格式为YYYYMMDDhhmmss
        txnAmt: p.money.toString(),
        reqReserved: JSON.stringify({ order: p.order })
      };
      axios({
        method: "post",
        url: this.config.url.back,
        data: this.sign(q)
      }).then(r => {
        const res = this.validate(r.data);
        resolve(res);
      }).catch(e => {
        resolve(new REQUEST({ err: "网络错误", raw: e }));
      });
    });
  }
  back(param: ORDER | oa): Promise<REQUEST> {
    return new Promise(async resolve => {
      const p = new ORDER(param);
      if (!p.order) return resolve(new REQUEST({ err: "参数错误", raw: p }));

      if (!(p.money > 0)) p.money = 0;
      if (!p.orderT) p.orderT = method.randStr("ylt"); // 生成新订单

      if (!p.frontUrl) p.frontUrl = this.config.frontUrl;
      if (!p.backUrl) p.backUrl = this.config.backUrl;
      if (p.channelType != "08") p.channelType = "07";
      if (!p.accessType) p.accessType = "0";
      if (!p.txnTime) p.txnTime = this.txnTime;
      if (!p.title) p.title = "退还定金";


      if (!p.queryId || !p.money) { // 确认流水号 总金额
        const o = await this.query(p);
        if (o.check) {
          p.queryId = o.raw.queryId as string;
          if (!p.money) {
            p.money = o.payment;
            // const res = await this.revoke(p); // 撤销交易
            // console.log(res);
            // if (res.err) {
            //   if (res.raw.respMsg) Object.assign(res, { err: res.raw.respMsg });
            //   resolve(res);
            //   return;
            // } else return resolve(res); // 撤销成功
          }
        } else return resolve(Object.assign(o, { err: "退款失败" }));
      }
      // resolve(new REQUEST({ err: "手动结束" }));

      const q: itf.Back = {
        version: this.config.version,
        encoding: this.config.encoding,
        signMethod: this.config.signMethod,
        txnType: "04",
        txnSubType: "00",
        bizType: "000201",
        accessType: p.accessType,
        channelType: p.channelType,
        backUrl: p.backUrl,
        orderId: p.orderT, // 新生成一个订单号
        merId: this.config.merId, // 商户id
        origQryId: p.queryId,
        txnTime: p.txnTime, // 被查询的交易的订单发送时间，格式为YYYYMMDDhhmmss
        txnAmt: p.money.toString(), // 退款金额
        reqReserved: JSON.stringify({ order: p.order })
      };
      // console.log(q);
      // return resolve(new REQUEST({ err: "手动结束" }));
      axios({
        method: "post",
        url: this.config.url.back,
        data: this.sign(q)
      }).then(r => {
        // resolve(new REQUEST("结束"));
        const res = this.validate(r.data);
        res.order = p.order;
        if (res.raw.respMsg) Object.assign(res, { err: res.raw.respMsg });
        resolve(res);
      }).catch(e => {
        resolve(new REQUEST({ err: "网络错误", raw: e }));
      });
    });
  }
  async notify(req: oa): Promise<REQUEST> {
    let res = await this.validate(req);
    if (res.check && typeof res.raw.reqReserved == "string") {
      res.raw.reqReserved = JSON.parse(res.raw.reqReserved);
    }
    return res;
  }
  front(req: oa): REQUEST {
    return this.validate(req);
  }
}
export default PAY;